
/*	fg 	- FRAME GRABBER 1.0		1.11.2011
*
* (conatins some code based on Dranger's code + Nezer's img_convert function supplied in the forum)
*
* THIS FRAME GRABBER SUPPORTS V4L2 WITH READING CAPABILITIES ACCORDING TO THE API IN THE REF. LINK FROM SCIPIO.ORG
* 
*/

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <stdio.h>
#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>		/* open */
#include <unistd.h>		/* exit */
#include <sys/ioctl.h>		/* ioctl */

#include <linux/videodev.h>	/*V4L2*/

//constants
#define START	1
#define STOP	0


//GLOBAL VARS
//these are global to simplify the code - since there is a background thread running later
//which needs access to these vars.
int Quit;
int Grab;

SDL_mutex *mutex;
SDL_cond *cond;

int file_desc, ret_val;
AVCodecContext  pCodecCtx;
AVFrame         pFrame;
AVPicture	pict; 
int             numBytes;
uint8_t         *buffer;
int		ctx[3];
SDL_Overlay     *bmp;
SDL_Surface     *screen;
SDL_Rect        rect;


//the function Nezer provided
void img_convert(AVPicture * target , int targetFmt, AVPicture * source ,int sourceFmt,int w, int h)
{
	static struct SwsContext *img_convert_ctx=NULL;
	if(img_convert_ctx == NULL)
	{
		img_convert_ctx = sws_getContext(w, h,
				sourceFmt, w, h, targetFmt, SWS_BICUBIC,NULL, NULL, NULL);
	}
	sws_scale(img_convert_ctx, source->data,
			source->linesize, 0,
			h, target->data, target->linesize);
}

/* parsing function used to handle input in the main thread*/
int handle_user_input(char *str){

	if (str == NULL) return 0;
	else if (*str == '\0') return 0;
	else if (strcmp("exit", str) == 0){
		Quit = 1;
  		
	}
	else if (strcmp("start", str) == 0){
		SDL_LockMutex(mutex);
		Grab = START;
		SDL_CondSignal(cond);
		SDL_UnlockMutex(mutex);
	}
	else if (strcmp("stop", str) == 0){
		SDL_LockMutex(mutex);
		Grab = STOP;
		SDL_UnlockMutex(mutex);
	}
	else if (strcmp("help", str) == 0){
		printf("commands:\nexit\nstart\nstop\n");
	}
	else printf("bad command, enter 'help' for a list of commands.\n");

	return 1;

}

/*the frame grabbing function for a thread running in the background*/
int grab_frames(void *arg) {
	while(1){
		SDL_LockMutex(mutex);
		if (Grab == STOP){
			SDL_CondWait(cond, mutex);
		}
		SDL_UnlockMutex(mutex);

		ret_val = read(file_desc, buffer, numBytes);
	
		//printf("ret_val: %d\tnumBytes: %d\n", ret_val, numBytes);fflush(stdout);
		if (ret_val == 0) {
			printf("couldn't read from device\n");
			SDL_LockMutex(mutex);
			Grab = STOP;
			SDL_UnlockMutex(mutex);
			continue;
		}
		img_convert(&pict, PIX_FMT_YUV420P, (AVPicture *)&pFrame, pCodecCtx.pix_fmt, pCodecCtx.width, pCodecCtx.height);

		SDL_LockYUVOverlay(bmp);
	
		bmp->pixels[0] = pict.data[0];
		bmp->pixels[2] = pict.data[1];
		bmp->pixels[1] = pict.data[2];

		bmp->pitches[0] = pict.linesize[0];
		bmp->pitches[2] = pict.linesize[1];
		bmp->pitches[1] = pict.linesize[2];

		SDL_UnlockYUVOverlay(bmp);

		rect.x = 0;
		rect.y = 0;
		rect.w = pCodecCtx.width;
		rect.h = pCodecCtx.height;
	
		SDL_DisplayYUVOverlay(bmp, &rect);

	}
	
	return 1;
}

/* this function takes the format we get from the driver and converts it to ffmpeg format code
 * the formats are up-to-date with the V4L2 site Nezer ref.
 *
 * input: 
 *	AVCodecContext *ctx:			the ffmpeg struct to populate with the info
 *      struct v4l2_pix_format *pix:		the V4L2 struct with the info the driver supplied
 *output:
 *	on output ctx will be populated with: width, height, and pix_fmt
 *	return 1 if ffmpeg supports the pixelformat given (0 otherwise)
 */
int convertFormat(AVCodecContext *ctx, struct v4l2_pix_format *pix){
	int t;

	if ((ctx == NULL) || (pix == NULL))
		return 0;

	ctx->width = pix->width;
	ctx->height = pix->height;

	switch(pix->pixelformat){
	case V4L2_PIX_FMT_RGB332:
		t = -1;
		break;
	case V4L2_PIX_FMT_RGB444:
		t = PIX_FMT_RGB444BE;
		break;
	case V4L2_PIX_FMT_RGB555:
		t = PIX_FMT_RGB555;
		break;
	case V4L2_PIX_FMT_RGB565:
		t = PIX_FMT_RGB565;
		break;
	case V4L2_PIX_FMT_RGB555X:
		t = -1;
		break;
	case V4L2_PIX_FMT_RGB565X:
		t = -1;
		break;
	case V4L2_PIX_FMT_BGR24:
		t = -1;
		break;
	case V4L2_PIX_FMT_RGB24:
		t = PIX_FMT_RGB24;
		break;
	case V4L2_PIX_FMT_BGR32:
		t = PIX_FMT_BGR32;
		break;
	case V4L2_PIX_FMT_RGB32:
		t = PIX_FMT_RGB32;
		break;
	case V4L2_PIX_FMT_GREY:
		t = -1;
		break;
	case V4L2_PIX_FMT_Y16:
		t = -1;
		break;
	case V4L2_PIX_FMT_PAL8:
		t = PIX_FMT_PAL8;
		break;
	case V4L2_PIX_FMT_YVU410:
		t = -1;
		break;
	case V4L2_PIX_FMT_YVU420:
		t = -1;
		break;
	case V4L2_PIX_FMT_YUYV:
		t = -1;
		break;
	case V4L2_PIX_FMT_UYVY:
		t = -1;
		break;
	case V4L2_PIX_FMT_YUV422P:
		t = PIX_FMT_YUV422P;
		break;
	case V4L2_PIX_FMT_YUV411P:
		t = PIX_FMT_YUV411P;
		break;
	case V4L2_PIX_FMT_Y41P:
		t = -1;
		break;
	case V4L2_PIX_FMT_YUV444:
		t = -1;
		break;
	case V4L2_PIX_FMT_YUV555:
		t = -1;
		break;
	case V4L2_PIX_FMT_YUV565:
		t = -1;
		break;
	case V4L2_PIX_FMT_YUV32:
		t = -1;
		break;
	case V4L2_PIX_FMT_NV12:
		t = PIX_FMT_NV12;
		break;
	case V4L2_PIX_FMT_NV21:
		t = PIX_FMT_NV21;
		break;
	case V4L2_PIX_FMT_YUV410:
		t = -1;
		break;
	case V4L2_PIX_FMT_YUV420:
		t = -1;
		break;
	case V4L2_PIX_FMT_YYUV:
		t = -1;
		break;
	case V4L2_PIX_FMT_HI240:
		t = -1;
		break;
	case V4L2_PIX_FMT_HM12:
		t = -1;
		break;
	case V4L2_PIX_FMT_SBGGR8:
		t = -1;
		break;
	case V4L2_PIX_FMT_SBGGR16:
		t = -1;
		break;
	case V4L2_PIX_FMT_MJPEG:
		t = -1;
		break;
	case V4L2_PIX_FMT_JPEG:
		t = -1;
		break;
	case V4L2_PIX_FMT_DV:
		t = -1;
		break;
	case V4L2_PIX_FMT_MPEG:
		t = -1;
		break;
	case V4L2_PIX_FMT_WNVA:
		t = -1;
		break;
	case V4L2_PIX_FMT_SN9C10X:
		t = -1;
		break;
	case V4L2_PIX_FMT_PWC1:
		t = -1;
		break;
	case V4L2_PIX_FMT_PWC2:
		t = -1;
		break;
	case V4L2_PIX_FMT_ET61X251:
		t = -1;
		break;
	default: 
		t = -1;
	}
	if (t == -1)
		return 0;

	ctx->pix_fmt = t;
	
	return 1;
	
}


int main(int argc, char **argv)
{
	struct v4l2_capability		cap;
	struct v4l2_format		fmt;

	char buff[1024];
	
	//initialize locking mechanism vars
	Quit = 0;
	Grab = 0;

	mutex = SDL_CreateMutex();
	cond = SDL_CreateCond();
	
	printf("\n\nFG - FRAME GRABBER V1.0\n\n\n");

	if (argc != 2){
		printf("please supply a file to work with (driver)\n");
		return 0;
	}

	printf("Please press right-click on the frame of the window that opend\nand press Always on top\n\n");

	//open the device and start working
	printf("opening device\n");
	file_desc = open(argv[1], O_RDONLY);	
	if (file_desc < 0) {
		printf("ERROR: Can't open device file: %s\n", argv[1]);
		goto err1;
	}

	//check if the driver supports read()
	printf("checking if the driver supports read()\n");
	ret_val = ioctl(file_desc, VIDIOC_QUERYCAP, &cap);
	if (ret_val < 0) {
		printf("ERROR: the driver does not support reading capability:%d\n", ret_val);
		goto err2;
	}
	if ((cap.capabilities & V4L2_CAP_READWRITE) != V4L2_CAP_READWRITE){
		printf("the driver doesn't support reading\n\n");
		goto err2;
	}

	//get format information from the driver
	printf("getting picture format from the driver\n\n");
	ret_val = ioctl(file_desc, VIDIOC_G_FMT, &fmt);
	if (ret_val < 0) {
		printf("ERROR: failed getting format info from the driver:%d\n", ret_val);
		goto err2;
	}
	if (convertFormat(&pCodecCtx, &fmt.fmt.pix) == 0){
		printf("ERROR: unsupported pixelformat\n");
		goto err2;
	}
	

	// Register all formats and codecs -- CODEC
	av_register_all();

	//initialize SDL
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		fprintf(stderr, "ERROR: Could not initialize SDL - %s\n", SDL_GetError());
		goto err2;
	}	

		// Make a screen to put our video
#ifndef __DARWIN__
	screen = SDL_SetVideoMode(pCodecCtx.width, pCodecCtx.height, 0, 0);
#else
	screen = SDL_SetVideoMode(pCodecCtx.width, pCodecCtx.height, 24, 0);
#endif
	if(!screen) {
		fprintf(stderr, "ERROR SDL: could not set video mode - exiting\n");
		goto err2;
	}

	// Allocate a place to put our YUV image on that screen
	bmp = SDL_CreateYUVOverlay(pCodecCtx.width, pCodecCtx.height, SDL_YV12_OVERLAY, screen);

	//an AVFrame for the image we display
	numBytes = avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx.width, pCodecCtx.height);
	buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
	avpicture_fill((AVPicture *)&pict, buffer, PIX_FMT_YUV420P, pCodecCtx.width, pCodecCtx.height);

	//an AVFrame for reading
	numBytes = avpicture_get_size(pCodecCtx.pix_fmt, pCodecCtx.width, pCodecCtx.height);
	buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
	avpicture_fill((AVPicture *)&pFrame, buffer, pCodecCtx.pix_fmt, pCodecCtx.width, pCodecCtx.height);
	
	//run a user command thread to read input
	SDL_CreateThread(grab_frames, NULL);


	printf("type-in 'help' for a command list\n\n");

	while (1){
		SDL_LockMutex(mutex);
		if (!Quit){
			SDL_UnlockMutex(mutex);
			if (fgets(buff, 1024, stdin) == NULL){
				printf("error reading\n");
			}
			buff[strlen(buff)-1] = '\0';
    			handle_user_input(buff);
			continue;
		}
		SDL_UnlockMutex(mutex);
		break;
	}


	
	printf("exiting the frame grabber\n");fflush(stdout);
err2:	
	close(file_desc);
err1:
	SDL_DestroyMutex(mutex);
	SDL_DestroyCond(cond);

	return 1;
}
